﻿using System.Globalization;
using Avalonia.Data.Converters;
using Avalonia.Media;
using GraphShape.Algorithms.Layout;
using GraphShape.Controls.Extensions;

namespace GraphShape.Controls.Converters
{
    /// <summary>
    /// Converter of position and sizes of the source and target points,
    /// and the route information of an edge to a path.
    /// </summary>
    /// <remarks>The edge can bend, or it can be straight line.</remarks>
    public class EdgeRouteToPathConverter : EdgeConverterBase, IMultiValueConverter
    {
        
        #region IMultiValueConverter

        /// <inheritdoc />
        /// <exception cref="T:System.ArgumentException">
        /// At least one of 9 arguments is missing.
        /// pos (1,2), size (3,4) of source; pos (5,6), size (7,8) of target; routeInformation (9)
        /// </exception>
        public object Convert(IList<object> values, Type targetType, object parameter, CultureInfo culture)
        {
            if (values is null)
                return new PathFigures();

            if (values.Count != 9)
            {
                throw new ArgumentException(
                    $"{nameof(EdgeRouteToPathConverter)} must have 9 parameters: pos (1,2), size (3,4) of source; pos (5,6), size (7,8) of target; routeInformation (9).",
                    nameof(values));
            }

            ExtractInputs(
                values,
                out var sourcePos,
                out var targetPos,
                out var sourceSize,
                out var targetSize,
                out var routeInformation);

            var hasRouteInfo = routeInformation != null && routeInformation.Length > 0;

            // Create the path
            var p1 = LayoutUtils.GetClippingPoint(
                                                  sourceSize,
                                                  sourcePos,
                                                  hasRouteInfo ? routeInformation[0].ToGraphShapePoint() : targetPos).ToPoint();

            var p2 = LayoutUtils.GetClippingPoint(
                                                  targetSize,
                                                  targetPos,
                                                  hasRouteInfo ? routeInformation[^1].ToGraphShapePoint() : sourcePos).ToPoint();


            var segments = new PathSegment[1 + (hasRouteInfo ? routeInformation.Length : 0)];
            
            if (hasRouteInfo)
            {
                for (var i = 0; i < routeInformation.Length; ++i)
                {
                    segments[i] = new LineSegment
                    {
                        Point = routeInformation[i],
                    };
                }
            }

            var pLast = hasRouteInfo ? routeInformation[^1] : p1;
            var v     = pLast - p2;
            v = v / GetLength(v) * 5;
            var n = new aVector(-v.Y, v.X) * 0.3;

            segments[^1] = new LineSegment
            {
                Point = p2 + v,
            };

            var pathCollection = new PathFigures
            {
                new PathFigure
                {
                    IsClosed   = false,
                    Segments   = GetSegments(segments),
                    StartPoint = p1,
                },
                new PathFigure
                {
                    StartPoint = p2,
                    Segments = new PathSegments
                    {
                        new LineSegment
                        {
                            Point = p2 + v - n,
                        },
                        new LineSegment
                        {
                            Point = p2 + v + n,
                        },
                    },
                    IsClosed = true,
                },
            };

            return pathCollection;
        }

        private static void ExtractInputs(
            [NotNull, ItemNotNull] IList<object> values,
            out gPoint sourcePos,
            out gPoint targetPos,
            out Size sourceSize,
            out Size targetSize,
            out aPoint[] routeInformation)
        {
            
            var v0 = values[0];
            var v1 = values[1];
            var v2 = values[2];
            var v3 = values[3];
            var v4 = values[4];
            var v5 = values[5];
            var v6 = values[6];
            var v7 = values[7];
            var v8 = values[8];
            // Get the position of the source
            sourcePos = new gPoint(
                                   v0 != AvaloniaProperty.UnsetValue ? (double) v0 : 0.0,
                                   v1 != AvaloniaProperty.UnsetValue ? (double) v1 : 0.0);

            // Get the size of the source
            sourceSize = new Size(
                v2 != AvaloniaProperty.UnsetValue ? (double) v2 : 0.0,
                v3 != AvaloniaProperty.UnsetValue ? (double) v3 : 0.0);

            // Get the position of the target
            targetPos = new gPoint(
                                   v4 != AvaloniaProperty.UnsetValue ? (double) v4 : 0.0,
                                   v5 != AvaloniaProperty.UnsetValue ? (double) v5 : 0.0);

            // Get the size of the target
            targetSize = new Size(
                v6 != AvaloniaProperty.UnsetValue ? (double) v6 : 0.0,
                v7 != AvaloniaProperty.UnsetValue ? (double) v7 : 0.0);

            // Get the route information
            routeInformation = values[8] != AvaloniaProperty.UnsetValue ? (aPoint[]) values[8] : null;
        }

        /// <exception cref="T:System.NotSupportedException">This method is not supported.</exception>
        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotSupportedException("Path to edge route conversion not supported.");
        }

        #endregion
    }
}